﻿using log4net;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace FlowSimulate {

    public class CostReader {

        private static readonly ILog LOG = LogManager.GetLogger(typeof(CostReader));

        public CostReader(string filepath) {
            this.filepath = filepath;
            (var tempData, var maxColumn) = this.ReadFile(filepath);
            this.data = CopyResult(tempData, maxColumn);
            LOG.Info($"Read costs from {filepath}");
        }

        private (List<float[]>, int) ReadFile(string filepath) {
            using var reader = new StreamReader(filepath, Encoding.UTF8);
            List<float[]> tempData = [];
            int maxColumn = 0;
            while (!reader.EndOfStream) {
                var line = reader.ReadLine();
                line = line.Trim();
                if (string.IsNullOrWhiteSpace(line))
                    continue;
                if (line.StartsWith("#! ")) {
                    this.ReadMeta(line);
                    continue;
                }
                if (line.StartsWith("#"))
                    continue;
                var lineData = this.ReadData(line);
                if (maxColumn < lineData.Length)
                    maxColumn = lineData.Length;
                tempData.Add(lineData);
            }
            return (tempData, maxColumn);
        }

        private static float[,] CopyResult(List<float[]> tempData, int maxColumn) {
            var result = new float[tempData.Count, maxColumn];
            for (int i=0; i<tempData.Count; i++) {
                var row = tempData[i];
                for (int j=0; j<maxColumn; j++)
                    result[i,j] = j < row.Length ? row[j] : 0;
            }
            return result;
        }

        private void ReadMeta(string line) {
            int colonIndex = line.IndexOf(':');
            var meta = line[3..colonIndex].ToUpper();
            Dictionary<string, int> dict = null;
            List<string> list = null;
            if (meta == "ROWS") {
                dict = this.rowIndexes;
                list = this.rows;
                LOG.Debug($"Read meta rows={meta}");
            } else if (meta == "COLUMNS") {
                dict = this.columnIndexes;
                list = this.columns;
                LOG.Debug($"Read meta columns={meta}");
            } else {
                LOG.Debug($"Ignored meta {line}");
                return;
            }

            dict.Clear();
            list.Clear();

            var rest = line[(colonIndex+1)..].Trim();
            var parts = rest.Split(',').Select(x => x.Trim());
            var index = 0;
            foreach (var part in parts) {
                list.Add(part);
                dict.Add(part, index++);
            }
        }

        private float[] ReadData(string line) {
            var parts = line.Split(',');
            List<float> list = [];
            foreach (var part in parts) {
                if (!float.TryParse(part, out var result))
                    list.Add(0);
                list.Add(result);
            }
            LOG.Debug($"Read data={line}");
            return [.. list];
        }

        readonly string filepath;

        readonly List<string> columns = [];
        readonly List<string> rows = [];

        readonly Dictionary<string, int> columnIndexes = [];
        readonly Dictionary<string, int> rowIndexes = [];

        readonly float[,] data;

        public int ColumnIndex(string columnName) {
            if (this.columnIndexes.ContainsKey(columnName))
                return this.columnIndexes[columnName];
            return -1;
        }

        public int RowIndex(string rowName) {
            if (this.rowIndexes.ContainsKey(rowName))
                return this.rowIndexes[rowName];
            return -1;
        }

        public string ColumnName(int index) {
            if (index < 0) return null;
            if (index >= this.columns.Count) return null;
            return this.columns[index];
        }

        public string RowName(int index) {
            if (index < 0) return null;
            if (index >= this.rows.Count) return null;
            return this.rows[index];
        }

        public float this[int row, int column] {
            get {
                if (row < 0 || column <0) return float.NaN;
                if (row >= this.rows.Count) return float.NaN;
                if (column >= this.columns.Count) return float.NaN;
                return this.data[row, column];
            }
        }

        public float this[string rowName, string columnName] {
            get {
                int row = this.RowIndex(rowName);
                int column = this.ColumnIndex(columnName);
                return this[row, column];
            }
        }

        private int[] FindMaxWidth() {
            int[] maxWidths = new int[this.columns.Count];
            for (int i=0; i<this.rows.Count; i++) {
                for (int j=0; j<this.columns.Count; j++) {
                    var maxWidth = maxWidths[j];
                    var data = this.data[i, j];
                    var text = data.ToString("0");
                    var width = text.Length;
                    if (maxWidth < width)
                        maxWidth = width;
                    maxWidths[j] = maxWidth;
                }
            }
            for (int j=0; j< this.columns.Count; j++) {
                var maxWidth = maxWidths[j];
                var titleWidth = this.columns[j].Length;
                if (maxWidth < titleWidth)
                    maxWidth = titleWidth;
                maxWidths[j] = maxWidth;
            }
            return maxWidths;
        }

        internal string PrintSelf() {
            var builder = new StringBuilder();
            var maxColumnWidths = this.FindMaxWidth();
            var rowHeaderWidth = this.rows.Select(x => x.Length).Max();

            builder.Append(new string(' ', rowHeaderWidth + 3));
            for (int j=0; j<this.columns.Count; j++) {
                var maxWidth = maxColumnWidths[j];
                var format = $"{{0,{maxWidth}}}  ";
                var text = string.Format(format, this.columns[j]);
                builder.Append(text);
            }
            builder.AppendLine();

            for (int i=0; i<this.rows.Count; i++) {
                var headerFormat = $"{{0, -{rowHeaderWidth}}}:  ";
                var rowHeader = string.Format(headerFormat, this.rows[i]);
                builder.Append(rowHeader);
                for (int j=0; j<this.columns.Count; j++) {
                    var maxWidth = maxColumnWidths[j];
                    var format = $"{{0,{maxWidth}}}  ";
                    var text = string.Format(format, this.data[i, j]);
                    builder.Append(text);
                }
                builder.AppendLine();
            }

            return builder.ToString();
        }
    }

}
